{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0ee97cf2",
   "metadata": {},
   "source": [
    "# Jingdong Extract Top 50 Powerbanks Review Info"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f2ece7ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from selenium import webdriver\n",
    "from selenium.webdriver.chrome.options import Options  # for change chrome setting \n",
    "from selenium.webdriver.chrome.service import Service  # for manage chromedriver\n",
    "from selenium.webdriver.common.by import By\n",
    "from selenium.webdriver.support.ui import WebDriverWait\n",
    "from selenium.webdriver.support import expected_conditions as EC\n",
    "import re\n",
    "import time\n",
    "import random\n",
    "import json\n",
    "from pprint import pprint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9080f8e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_chromedriver(webdriver_path=\"chromedriver.exe\",\n",
    "                     disable_logging=True,\n",
    "                     headless=False,\n",
    "                     user_agent=None,\n",
    "                     disable_automation_features=True,\n",
    "                     patch_webdriver_property=True,\n",
    "                     implicit_wait=10):\n",
    "    \"\"\"\n",
    "    Launch the Chrome browser's WebDriver.\n",
    "    启动 Chrome 浏览器的 WebDriver。\n",
    "\n",
    "    Args:\n",
    "        webdriver_path (str): Path to chromedriver.exe. Defaults to current directory.\n",
    "                              chromedriver.exe 的路径，默认当前目录。\n",
    "        disable_logging (bool): Whether to suppress Chrome logs. Defaults to True.\n",
    "                                是否禁用控制台日志输出，默认 True。\n",
    "        headless (bool): Whether to run Chrome in headless mode (no GUI). Defaults to False.\n",
    "                         是否以无头模式运行浏览器，默认 False。\n",
    "        user_agent (str or None): Custom User-Agent string. If None, no override.\n",
    "                                  Can use https://www.whatismybrowser.com/detect/what-is-my-user-agent/ to find your current User-Agent.\n",
    "                                  自定义 User-Agent 字符串，默认 None 表示不更改。\n",
    "                                  可以使用 https://www.whatismybrowser.com/detect/what-is-my-user-agent/ 来查看当前 User-Agent。\n",
    "        disable_automation_features (bool): Disable automation flags to bypass detection. Defaults to True.\n",
    "                                            是否关闭 Chrome 的自动化特征（例如自动扩展），默认 True。\n",
    "        patch_webdriver_property (bool): Patch navigator.webdriver to avoid detection. Defaults to True.\n",
    "                                         是否将 navigator.webdriver 设置为 undefined，默认 True。\n",
    "        implicit_wait (int): Global implicit wait time in seconds. Defaults to 10.\n",
    "                             全局隐式等待时间（秒），默认 10 秒。\n",
    "\n",
    "    Returns:\n",
    "        webdriver.Chrome: Initialized Chrome WebDriver instance.\n",
    "                          已初始化的 Chrome WebDriver 实例。\n",
    "    \"\"\"\n",
    "    options = Options()\n",
    "\n",
    "    # Suppress Chrome logs\n",
    "    # 禁用 Chrome 日志输出\n",
    "    if disable_logging:\n",
    "        options.add_experimental_option(\"excludeSwitches\", [\"enable-logging\"])\n",
    "\n",
    "    # Enable headless mode (no browser UI)\n",
    "    # 启用无头模式（不显示浏览器界面）\n",
    "    if headless:\n",
    "        options.add_argument(\"--headless\")\n",
    "        options.add_argument(\"--disable-gpu\")\n",
    "        options.add_argument(\"--window-size=1920x1080\")\n",
    "\n",
    "    # Set custom User-Agent if provided\n",
    "    # 设置自定义 User-Agent（如果传入）\n",
    "    if user_agent is not None:\n",
    "        options.add_argument(f\"user-agent={user_agent}\")\n",
    "\n",
    "    # Disable automation detection features\n",
    "    # 关闭自动化检测相关特征\n",
    "    if disable_automation_features:\n",
    "        options.add_argument(\"--disable-blink-features=AutomationControlled\")\n",
    "        options.add_experimental_option(\"useAutomationExtension\", False)\n",
    "        options.add_experimental_option(\"excludeSwitches\", [\"enable-automation\"])\n",
    "\n",
    "    # Initialize WebDriver\n",
    "    # 初始化 WebDriver\n",
    "    wd = webdriver.Chrome(service=Service(webdriver_path), options=options)\n",
    "\n",
    "    # Apply implicit wait\n",
    "    # 设置全局隐式等待时间\n",
    "    wd.implicitly_wait(implicit_wait)\n",
    "\n",
    "    # Patch navigator.webdriver = undefined to evade detection\n",
    "    # 修改 navigator.webdriver 为 undefined 以规避检测\n",
    "    if patch_webdriver_property:\n",
    "        wd.execute_cdp_cmd(\"Page.addScriptToEvaluateOnNewDocument\", {\n",
    "            \"source\": \"\"\"\n",
    "                Object.defineProperty(navigator, 'webdriver', {\n",
    "                    get: () => undefined\n",
    "                })\n",
    "            \"\"\"\n",
    "        })\n",
    "\n",
    "    return wd\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "fa0fee5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def random_sleep(min_sec=9.0, max_sec=12.0):\n",
    "    \"\"\"\n",
    "    Sleep for a random duration between min_sec and max_sec.\n",
    "    Used to simulate human behavior and avoid detection as an automated script.\n",
    "    \n",
    "    在 min_sec 到 max_sec 之间随机 sleep 一段时间。\n",
    "    用于模拟人类行为，避免被检测为自动脚本。\n",
    "\n",
    "    Args:\n",
    "        min_sec (float): Minimum sleep duration in seconds.\n",
    "                         最小睡眠时间（秒），默认 9.0 秒。\n",
    "        max_sec (float): Maximum sleep duration in seconds.\n",
    "                         最大睡眠时间（秒），默认 12.0 秒。\n",
    "    \"\"\"\n",
    "    delay = random.uniform(min_sec, max_sec)\n",
    "    time.sleep(delay)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5bb4c79c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def clean_and_add_cookies(wd, cookie_path):\n",
    "    \"\"\"\n",
    "    Load cookies from a JSON file and add them to the WebDriver instance.\n",
    "    从 JSON 文件加载 Cookie 并添加到 WebDriver 实例中。\n",
    "\n",
    "    Args:\n",
    "        wd (webdriver.Chrome): \n",
    "            The initialized Chrome WebDriver instance. \n",
    "            已初始化的 Chrome WebDriver 实例。\n",
    "        cookie_path (str): \n",
    "            Path to the cookie file in JSON format.\n",
    "            Cookie 文件的路径，格式为 JSON。\n",
    "    \"\"\"\n",
    "    # Open the cookie file and load cookies\n",
    "    # 打开 Cookie 文件并加载 Cookie\n",
    "    with open(cookie_path, \"r\", encoding=\"utf-8\") as f:\n",
    "            cookies = json.load(f)\n",
    "        \n",
    "    # Clean the cookie data: Cookies may have None or \"no_restriction\" values that need to be converted to valid values\n",
    "    # 清洗 Cookie 数据: Cookie 中有些值为 None 或 \"no_restriction\"，需要转换为合法值\n",
    "    for c in cookies:\n",
    "        # Convert \"no_restriction\" or None to valid values\n",
    "        # 将 \"no_restriction\" 或 None 转换为合法值\n",
    "        if c.get(\"sameSite\") not in [\"Strict\", \"Lax\", \"None\"]:\n",
    "            c[\"sameSite\"] = \"None\"\n",
    "        # # Selenium does not accept null values, so we need to remove these keys\n",
    "        # # Selenium 不接受 null，需要删除这些键\n",
    "        # for key in list(c.keys()):\n",
    "        #     if c[key] is None:\n",
    "        #         del c[key]\n",
    "    \n",
    "    # Add cookies to the WebDriver\n",
    "    # 将 Cookie 添加到 WebDriver\n",
    "    for ck in cookies:\n",
    "        wd.add_cookie(ck)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec7d90d8",
   "metadata": {},
   "source": [
    "## Extract Top 50 Powerbanks SKUs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b88b8706",
   "metadata": {},
   "outputs": [],
   "source": [
    "ua = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\"\n",
    "\n",
    "# Initialize the Chrome WebDriver\n",
    "driver = get_chromedriver(\n",
    "    webdriver_path=\"../../chromedriver.exe\",\n",
    "    disable_logging=True,\n",
    "    headless=False,\n",
    "    user_agent=ua,\n",
    "    disable_automation_features=True,\n",
    "    patch_webdriver_property=True,\n",
    "    implicit_wait=30\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8230f584",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Navigating to JD homepage...\n",
      "Loading cookies from file...\n",
      "Refreshing the page to apply cookies...\n"
     ]
    }
   ],
   "source": [
    "cookie_path = \"../data/jd_cache.json\"\n",
    "# jd_category_page = \"https://list.jd.com/list.html?cat=9987,830,13658\"\n",
    "jd_category_page = \"https://list.jd.com/list.html?cat=9987%2C830%2C13658&psort=3&psort=3&0.7649336460749802#J_main\"\n",
    "jd_skus_path = \"../data/jd_top50_skus.csv\"\n",
    "\n",
    "# Navigate to the JD homepage\n",
    "print(\"Navigating to JD homepage...\")\n",
    "driver.get(\"https://www.jd.com/?country=USA\")\n",
    "random_sleep()\n",
    "\n",
    "# Load cookies from the JSON file and add them to the WebDriver\n",
    "print(\"Loading cookies from file...\")\n",
    "clean_and_add_cookies(driver, cookie_path)\n",
    "\n",
    "# Refresh the page to ensure cookies are applied\n",
    "print(\"Refreshing the page to apply cookies...\")\n",
    "driver.refresh()\n",
    "random_sleep()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0ca2535",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Navigating to JD category page...\n"
     ]
    }
   ],
   "source": [
    "# Navigate to the JD category page\n",
    "print(\"Navigating to JD category page...\")\n",
    "driver.get(jd_category_page)\n",
    "random_sleep()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "272b1897",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Items before scrolling: 30\n",
      "Items after scrolling: 60\n"
     ]
    }
   ],
   "source": [
    "# Get the current items count before scrolling\n",
    "items_before = len(driver.find_elements(By.CSS_SELECTOR, \"#J_goodsList .gl-item\"))\n",
    "# items_before = len(driver.find_elements(By.XPATH, '//*[@id=\"J_goodsList\"]//li[contains(@class, \"gl-item\")]'))\n",
    "print(f\"Items before scrolling: {items_before}\")\n",
    "\n",
    "# Scroll to the bottom of the page to load more products\n",
    "driver.execute_script(\"window.scrollTo(0, document.body.scrollHeight);\")\n",
    "random_sleep()\n",
    "\n",
    "items_after = len(driver.find_elements(By.CSS_SELECTOR, \"#J_goodsList .gl-item\"))\n",
    "print(f\"Items after scrolling: {items_after}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "59ace9d5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total items found: 60\n",
      "Item 1: SKU = 100024102298, Store = 罗马仕京东自营旗舰店\n",
      "Item 2: SKU = 100096418777, Store = 小米京东自营旗舰店\n",
      "Item 3: SKU = 100097634241, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 4: SKU = 100073745484, Store = 罗马仕京东自营旗舰店\n",
      "Item 5: SKU = 100140584252, Store = 倍思（Baseus）京东自营旗舰店\n",
      "Item 6: SKU = 100105445886, Store = 绿联（UGREEN）京东自营旗舰店\n",
      "Item 7: SKU = 100200931898, Store = 罗马仕京东自营旗舰店\n",
      "Item 8: SKU = 100157733819, Store = 罗马仕京东自营旗舰店\n",
      "Item 9: SKU = 100199404088, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 10: SKU = 10127956763136, Store = epcbook京东自营官方旗舰店\n",
      "Item 11: SKU = 100133826752, Store = 京东京造自营旗舰店\n",
      "Item 12: SKU = 100042985009, Store = 倍思（Baseus）京东自营旗舰店\n",
      "Item 13: SKU = 100031532138, Store = 小米京东自营旗舰店\n",
      "Item 14: SKU = 100144381044, Store = 绿联（UGREEN）京东自营旗舰店\n",
      "Item 15: SKU = 100044819549, Store = 罗马仕京东自营旗舰店\n",
      "Item 16: SKU = 10142242864010, Store = IMF旗舰店\n",
      "Item 17: SKU = 10090856414152, Store = 超级马官方旗舰店\n",
      "Item 18: SKU = 100047622232, Store = 睿量（REMAX）京东自营旗舰店\n",
      "Item 19: SKU = 10129004694713, Store = 炫威达旗舰店\n",
      "Item 20: SKU = 100063803015, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 21: SKU = 100172187106, Store = 倍思（Baseus）京东自营旗舰店\n",
      "Item 22: SKU = 100066290036, Store = 绿联（UGREEN）京东自营旗舰店\n",
      "Item 23: SKU = 100109593258, Store = Anker京东自营旗舰店\n",
      "Item 24: SKU = 10058663112762, Store = IMEV官方旗舰店\n",
      "Item 25: SKU = 10129866147676, Store = 闪科威旗舰店\n",
      "Item 26: SKU = 100120245055, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 27: SKU = 100135539621, Store = 小米京东自营旗舰店\n",
      "Item 28: SKU = 100121760530, Store = 荣耀京东自营旗舰店\n",
      "Item 29: SKU = 100061224749, Store = 品胜京东自营旗舰店\n",
      "Item 30: SKU = 100047464848, Store = 倍思（Baseus）京东自营旗舰店\n",
      "Item 31: SKU = 10129866147676, Store = 闪科威旗舰店\n",
      "Item 32: SKU = 100075439621, Store = 罗马仕京东自营旗舰店\n",
      "Item 33: SKU = 100125366365, Store = Anker京东自营旗舰店\n",
      "Item 34: SKU = 100149010326, Store = Anker京东自营旗舰店\n",
      "Item 35: SKU = 100106099450, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 36: SKU = 100016436144, Store = 华为京东自营旗舰店\n",
      "Item 37: SKU = 100200931898, Store = 罗马仕京东自营旗舰店\n",
      "Item 38: SKU = 10144325775428, Store = 中诺 CHINOE手机配件旗舰店\n",
      "Item 39: SKU = 100105445930, Store = 绿联（UGREEN）京东自营旗舰店\n",
      "Item 40: SKU = 100200931926, Store = 倍思（Baseus）京东自营旗舰店\n",
      "Item 41: SKU = 100213757200, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 42: SKU = 10102333929747, Store = Zokd旗舰店\n",
      "Item 43: SKU = 10129004694713, Store = 炫威达旗舰店\n",
      "Item 44: SKU = 100060192443, Store = 倍思（Baseus）京东自营旗舰店\n",
      "Item 45: SKU = 100110785840, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 46: SKU = 10119956611544, Store = 得意影音娱乐专营店\n",
      "Item 47: SKU = 10141253217046, Store = 米链手机数码配件工作室\n",
      "Item 48: SKU = 100133267851, Store = CUKTECH酷态科京东自营旗舰店\n",
      "Item 49: SKU = 10136859742730, Store = 犀狼得意专卖店\n",
      "Item 50: SKU = 10060515593137, Store = 电沃达旗舰店\n"
     ]
    }
   ],
   "source": [
    "# Get the full list of items after scrolling\n",
    "items = driver.find_elements(By.CSS_SELECTOR, \"#J_goodsList .gl-item\")\n",
    "print(f\"Total items found: {len(items)}\")\n",
    "\n",
    "# Extract SKU and store information from the items\n",
    "sku_store_list = []\n",
    "\n",
    "for i, item in enumerate(items):\n",
    "    if i >= 50:\n",
    "        break\n",
    "    \n",
    "    sku = item.get_attribute(\"data-sku\")\n",
    "    \n",
    "    try:\n",
    "        store = item.find_element(By.CSS_SELECTOR, \".p-shop\").text.strip()\n",
    "    except: \n",
    "        store = \"\"\n",
    "\n",
    "    if sku:\n",
    "        sku_store_list.append({\n",
    "            \"SKU\": sku,\n",
    "            \"Store\": store\n",
    "        })\n",
    "        print(f\"Item {i+1}: SKU = {sku}, Store = {store}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "ed9e545f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(sku_store_list)\n",
    "df.to_csv(jd_skus_path, index=False, encoding=\"utf-8-sig\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "652bc366",
   "metadata": {},
   "outputs": [],
   "source": [
    "driver.quit()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19ba7d74",
   "metadata": {},
   "source": [
    "## Extract Review Info"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d0368ed3",
   "metadata": {},
   "outputs": [],
   "source": [
    "def parse_count(text: str) -> int:\n",
    "    \"\"\"\n",
    "    Parse a Chinese count string like '2万+', '200+' or '27' into an integer.\n",
    "    解析中文计数字符串，如 '2万+'、'200+' 或 '27'，返回整数。\n",
    "    \n",
    "    Args:\n",
    "        text (str): The count string to parse.\n",
    "                    要解析的计数字符串。\n",
    "    \n",
    "    Returns:\n",
    "        int: The parsed integer value.\n",
    "             解析后的整数值。\n",
    "    \"\"\"\n",
    "    text = text.rstrip('+')\n",
    "    if text.endswith('万'):\n",
    "        # convert \"x万\" to integer\n",
    "        num = float(text[:-1])\n",
    "        return int(num * 10000)\n",
    "    return int(text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac966299",
   "metadata": {},
   "source": [
    "### Test for one product"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "7f343c3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "ua = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\"\n",
    "\n",
    "# Initialize the Chrome WebDriver\n",
    "driver = get_chromedriver(\n",
    "    webdriver_path=\"../../chromedriver.exe\",\n",
    "    disable_logging=True,\n",
    "    headless=False,\n",
    "    user_agent=ua,\n",
    "    disable_automation_features=True,\n",
    "    patch_webdriver_property=True,\n",
    "    implicit_wait=30\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a883a060",
   "metadata": {},
   "outputs": [],
   "source": [
    "cookie_path = \"../data/jd_cache.json\"\n",
    "jd_skus_path = \"../data/jd_top50_skus.csv\"\n",
    "\n",
    "# Navigate to the JD homepage\n",
    "driver.get(\"https://www.jd.com/?country=USA\")\n",
    "random_sleep(12, 15)\n",
    "\n",
    "# Load cookies from the JSON file and add them to the WebDriver\n",
    "clean_and_add_cookies(driver, cookie_path)\n",
    "\n",
    "# Refresh the page to ensure cookies are applied\n",
    "driver.refresh()\n",
    "random_sleep(12, 15)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "affc1e09",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Navigate to the JD powerbank product page\n",
    "sku = \"100024102298\"\n",
    "store = \"罗马仕京东自营旗舰店\"\n",
    "url = f\"https://item.jd.com/{sku}.html\"\n",
    "driver.get(url)\n",
    "random_sleep()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ba7bdb4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Product Name: 罗马仕（ROMOSS）55W自带线充电宝【京东首发】20000毫安快充移动电源 可上飞机适用于小米/苹果/红米/笔记本 黑色\n"
     ]
    }
   ],
   "source": [
    "# initialize with defaults\n",
    "review = {\n",
    "    \"SKU\": sku, \n",
    "    \"product_name\": \"\",\n",
    "    \"store\": store,\n",
    "    \"positive_rate\": \"0%\",\n",
    "    \"positive_count\": 0,\n",
    "    \"neutral_count\": 0,\n",
    "    \"negative_count\": 0,\n",
    "    \"negative_review_1\": \"\", \n",
    "    \"negative_review_2\": \"\",\n",
    "    \"negative_review_3\": \"\",\n",
    "    \"negative_review_4\": \"\",\n",
    "    \"negative_review_5\": \"\"\n",
    "}\n",
    "\n",
    "# save the product name\n",
    "review[\"product_name\"] = driver.find_element(\n",
    "    By.XPATH, '/html/body/div[4]/div/div[2]/div/div[5]/div[1]/span[1]'\n",
    ").text.strip()\n",
    "\n",
    "print(f\"Product Name: {review['product_name']}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "79459535",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Click the \"All Reviews\" button to expand the reviews section\n",
    "all_reviews_button = WebDriverWait(driver, 30).until(\n",
    "    EC.element_to_be_clickable(\n",
    "        (By.XPATH, '//*[@id=\"comment-root\"]/div[3]/div')\n",
    "    )\n",
    ")\n",
    "all_reviews_button.click()\n",
    "random_sleep()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "b334098b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total review tags found: 15\n"
     ]
    }
   ],
   "source": [
    "# wait for the review tags overlay\n",
    "rate_list =  WebDriverWait(driver, 30).until(\n",
    "    EC.visibility_of_element_located((By.ID, \"rateList\"))\n",
    ")\n",
    "tag_divs = driver.find_elements(\n",
    "    By.XPATH, '//*[@id=\"rateList\"]/div/div[2]/div/div'\n",
    ")\n",
    "random_sleep()\n",
    "\n",
    "print(f\"Total review tags found: {len(tag_divs)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "adc3df65",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'SKU': '100157733819', 'product_name': '罗马仕（ROMOSS）55W自带线充电宝【京东首发】20000毫安快充移动电源 可上飞机适用于小米/苹果/红米/笔记本 黑色', 'positive_rate': '97%', 'positive_count': 20000, 'neutral_count': 200, 'negative_count': 200, 'negative_review_1': '', 'negative_review_2': '', 'negative_review_3': '', 'negative_review_4': '', 'negative_review_5': ''}\n"
     ]
    }
   ],
   "source": [
    "# parse each tag\n",
    "for tag in tag_divs:\n",
    "    label = tag.find_element(By.XPATH, './span[1]').text.strip()\n",
    "    value = tag.find_element(By.XPATH, './span[2]').text.strip()\n",
    "\n",
    "    if label == \"全部\":\n",
    "        m = re.match(r'(\\d+\\.?\\d*)%', value)\n",
    "        review[\"positive_rate\"] = (m.group(1) + \"%\") if m else \"0%\"\n",
    "    elif label == \"好评\":\n",
    "        review[\"positive_count\"] = parse_count(value)\n",
    "    elif label == \"中评\":\n",
    "        review[\"neutral_count\"] = parse_count(value)\n",
    "    elif label == \"差评\":\n",
    "        review[\"negative_count\"] = parse_count(value)\n",
    "        tag.click()\n",
    "        random_sleep()\n",
    "\n",
    "print(review)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "9de284f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Negative Review 1: 比起我买的其它牌子10000m.的比这个20000m还能冲多几次电. 不建议购买\n",
      "Negative Review 2: 充电慢，说的有线，送来没有线\n",
      "Negative Review 3: 其他特色：充电中时不时停止充电\n",
      "Negative Review 4: 不支持vivo闪充 呵呵\n",
      "Negative Review 5: 不怎么样，缝隙挺大的，已经退货\n"
     ]
    }
   ],
   "source": [
    "# if there are bad reviews, scroll to load up to 5 items\n",
    "if review[\"negative_count\"] > 0:\n",
    "    # the scrollable container for review list\n",
    "    scrollable_div = driver.find_element(\n",
    "        By.XPATH, '//*[@id=\"rateList\"]/div/div[3]'\n",
    "    )\n",
    "    # scroll down a few times to load more reviews\n",
    "    for _ in range(5):\n",
    "        driver.execute_script(\"arguments[0].scrollTop += 300\", scrollable_div)\n",
    "        random_sleep(2, 3)\n",
    "\n",
    "    # extract up to 5 bad review texts\n",
    "    for i in range(1, 6):\n",
    "        try:\n",
    "            review_xpath = f'//*[@id=\"rateList\"]/div/div[3]/div[3]/div/div/div/div/div[{i}]/div/div/div[2]/div[2]/span'\n",
    "            review_elem = driver.find_element(By.XPATH, review_xpath)\n",
    "            review[f\"negative_review_{i}\"] = review_elem.text.strip()\n",
    "        except:\n",
    "            # no more reviews at this index\n",
    "            review[f\"negative_review_{i}\"] = \"\"\n",
    "\n",
    "for i in range(1, 6):\n",
    "    print(f\"Negative Review {i}: {review[f'negative_review_{i}']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f73833f",
   "metadata": {},
   "source": [
    "### Test for multiple products"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "435f100d",
   "metadata": {},
   "outputs": [],
   "source": [
    "ua = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\"\n",
    "\n",
    "# Initialize the Chrome WebDriver\n",
    "driver = get_chromedriver(\n",
    "    webdriver_path=\"../../chromedriver.exe\",\n",
    "    disable_logging=True,\n",
    "    headless=False,\n",
    "    user_agent=ua,\n",
    "    disable_automation_features=True,\n",
    "    patch_webdriver_property=True,\n",
    "    implicit_wait=30\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "b6737840",
   "metadata": {},
   "outputs": [],
   "source": [
    "cookie_path = \"../data/jd_cache.json\"\n",
    "jd_skus_path = \"../data/jd_top50_skus.csv\"\n",
    "\n",
    "# Navigate to the JD homepage\n",
    "driver.get(\"https://www.jd.com/?country=USA\")\n",
    "random_sleep()\n",
    "\n",
    "# Load cookies from the JSON file and add them to the WebDriver\n",
    "clean_and_add_cookies(driver, cookie_path)\n",
    "\n",
    "# Refresh the page to ensure cookies are applied\n",
    "driver.refresh()\n",
    "random_sleep()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6dd29e1f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing item 15/50: SKU = 10146872281876\n"
     ]
    }
   ],
   "source": [
    "start_idx = 14\n",
    "end_idx = 15\n",
    "jd_reviews_path = f\"../data/jd_reviews_{start_idx+1:02d}_{end_idx:02d}.csv\"\n",
    "\n",
    "# Read the SKU list from the CSV file\n",
    "df = pd.read_csv(jd_skus_path, encoding=\"utf-8-sig\")\n",
    "\n",
    "# Store the reviews information\n",
    "reviews_info = []\n",
    "for idx, row in df.iterrows():\n",
    "    if idx < start_idx or idx >= end_idx:\n",
    "        continue\n",
    "    \n",
    "    # Extract SKU and Store from the DataFrame row\n",
    "    sku = row[\"SKU\"]\n",
    "    store = row[\"Store\"]\n",
    "    print(f\"Processing item {idx + 1}/{len(df)}: SKU = {sku}\")\n",
    "    \n",
    "    review = {\n",
    "        \"SKU\": sku, \n",
    "        \"product_name\": \"\",\n",
    "        \"store\": store,\n",
    "        \"positive_rate\": \"0%\",\n",
    "        \"positive_count\": 0,\n",
    "        \"neutral_count\": 0,\n",
    "        \"negative_count\": 0,\n",
    "        \"negative_review_1\": \"\",\n",
    "        \"negative_review_2\": \"\",\n",
    "        \"negative_review_3\": \"\",\n",
    "        \"negative_review_4\": \"\",\n",
    "        \"negative_review_5\": \"\"\n",
    "    }\n",
    "\n",
    "    # Save the product name\n",
    "    review[\"product_name\"] = driver.find_element(\n",
    "        By.XPATH, '/html/body/div[4]/div/div[2]/div/div[5]/div[1]/span[1]'\n",
    "    ).text.strip()\n",
    "    \n",
    "    # Click the \"All Reviews\" button to expand the reviews section\n",
    "    all_reviews_button = WebDriverWait(driver, 30).until(\n",
    "        EC.element_to_be_clickable(\n",
    "            (By.XPATH, '//div[@id=\"comment-root\"]/div[@class=\"all-btn\"]')\n",
    "        )\n",
    "    )\n",
    "    all_reviews_button.click()\n",
    "    random_sleep()\n",
    "\n",
    "    # Wait for the review tags overlay\n",
    "    rate_list =  WebDriverWait(driver, 30).until(\n",
    "        EC.visibility_of_element_located((By.ID, \"rateList\"))\n",
    "    )\n",
    "    tag_divs = driver.find_elements(\n",
    "        By.XPATH, '//*[@id=\"rateList\"]/div/div[2]/div/div'\n",
    "    )\n",
    "    random_sleep()\n",
    "    \n",
    "    # Parse each tag\n",
    "    for tag in tag_divs:\n",
    "        label = tag.find_element(By.XPATH, './span[1]').text.strip()\n",
    "        value = tag.find_element(By.XPATH, './span[2]').text.strip()\n",
    "\n",
    "        if label == \"全部\":\n",
    "            m = re.match(r'(\\d+\\.?\\d*)%', value)\n",
    "            review[\"positive_rate\"] = (m.group(1) + \"%\") if m else \"0%\"\n",
    "        elif label == \"好评\":\n",
    "            review[\"positive_count\"] = parse_count(value)\n",
    "        elif label == \"中评\":\n",
    "            review[\"neutral_count\"] = parse_count(value)\n",
    "        elif label == \"差评\":\n",
    "            review[\"negative_count\"] = parse_count(value)\n",
    "            # Click the \"差评\" tag to load negative reviews\n",
    "            tag.click()\n",
    "            random_sleep()\n",
    "    \n",
    "    # if there are bad reviews, scroll to load up to 5 items\n",
    "    if review[\"negative_count\"] > 0:\n",
    "        # the scrollable container for review list\n",
    "        scrollable_div = driver.find_element(\n",
    "            By.XPATH, '//*[@id=\"rateList\"]/div/div[3]'\n",
    "        )\n",
    "        # scroll down a few times to load more reviews\n",
    "        for _ in range(5):\n",
    "            driver.execute_script(\"arguments[0].scrollTop += 300\", scrollable_div)\n",
    "            random_sleep(2, 3)\n",
    "\n",
    "        # extract up to 5 bad review texts\n",
    "        for i in range(1, 6):\n",
    "            try:\n",
    "                review_xpath = f'//*[@id=\"rateList\"]/div/div[3]/div[@class=\"_list_1ygkr_67\"]/div/div/div/div/div[{i}]/div/div/div[2]/div[2]/span'\n",
    "                review_elem = driver.find_element(By.XPATH, review_xpath)\n",
    "                review[f\"negative_review_{i}\"] = review_elem.text.strip()\n",
    "            except:\n",
    "                # no more reviews at this index\n",
    "                review[f\"negative_review_{i}\"] = \"\"\n",
    "    \n",
    "    # Append the review to the list\n",
    "    reviews_info.append(review)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "37f3ec6a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'SKU': 10146872281876,\n",
      "  'negative_count': 3,\n",
      "  'negative_review_1': '差评，原装自带的线不是快充，边冲边用，电量不长',\n",
      "  'negative_review_2': '充电宝非常垃圾，充满电用不了两次就没电了！还说2万的！连5000的都比不了！购买的时候一定要注意！',\n",
      "  'negative_review_3': '我真的要笑死了。标个小米跟我说适用于小米机型，我是冲着小米机型来买的吗？',\n",
      "  'negative_review_4': '',\n",
      "  'negative_review_5': '',\n",
      "  'neutral_count': 0,\n",
      "  'positive_count': 4,\n",
      "  'positive_rate': '56%',\n",
      "  'product_name': '小米（MI)米家适用66W充电宝20000毫安超大容量双向超级快充便携户外移动电源 商务黑【快充】 20000毫安'}]\n"
     ]
    }
   ],
   "source": [
    "pprint(reviews_info)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "acde8b6e",
   "metadata": {},
   "outputs": [],
   "source": [
    "driver.quit()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52986b7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Export to CSV file\n",
    "reviews_df = pd.DataFrame(reviews_info)\n",
    "reviews_df.to_csv(jd_reviews_path, index=False, encoding=\"utf-8-sig\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e43d9319",
   "metadata": {},
   "source": [
    "### Test for functions in .py file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e3e2f9e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from jd_pb_50review import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b5cc200e",
   "metadata": {},
   "outputs": [],
   "source": [
    "webdriver_path = \"../../chromedriver.exe\"\n",
    "cookie_path = \"../data/jd_cache.json\"\n",
    "category_url = \"https://list.jd.com/list.html?cat=9987%2C830%2C13658&psort=3&psort=3&0.7649336460749802#J_main\"\n",
    "jd_skus_path = \"../data/jd_top50_skus.csv\"\n",
    "ua = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "163d2b11",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing item 46/50: SKU = 10119956611544\n",
      "Processing item 47/50: SKU = 10141253217046\n",
      "Processing item 48/50: SKU = 100133267851\n",
      "Processing item 49/50: SKU = 10136859742730\n",
      "Processing item 50/50: SKU = 10060515593137\n",
      "Saved reviews for SKUs 46 to 50 into ../data/jd_reviews_46_50.csv\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[{'SKU': 10119956611544,\n",
       "  'product_name': '威顿【66W超级快充丨国家3C认证】充电宝20000毫安适用小米移动电源大容量自带线户外可上飞机小巧便携 【皓月白】20000毫安+品牌电芯+快至当日达 2025年新款丨自带三快充线丨国家3C认证',\n",
       "  'store': '得意影音娱乐专营店',\n",
       "  'positive_rate': '99%',\n",
       "  'positive_count': 2000,\n",
       "  'neutral_count': 0,\n",
       "  'negative_count': 3,\n",
       "  'negative_review_1': '充了几个小时还是1%垃圾充电宝，',\n",
       "  'negative_review_2': '',\n",
       "  'negative_review_3': '',\n",
       "  'negative_review_4': '',\n",
       "  'negative_review_5': ''},\n",
       " {'SKU': 10141253217046,\n",
       "  'product_name': '充电宝20000毫安大容量超级快充自带线可上飞机移动电源适用小米 蓝-标准版【自带四线+普通快充】 【极速发货/次日达】可上飞机',\n",
       "  'store': '米链手机数码配件工作室',\n",
       "  'positive_rate': '99%',\n",
       "  'positive_count': 1000,\n",
       "  'neutral_count': 1,\n",
       "  'negative_count': 0,\n",
       "  'negative_review_1': '',\n",
       "  'negative_review_2': '',\n",
       "  'negative_review_3': '',\n",
       "  'negative_review_4': '',\n",
       "  'negative_review_5': ''},\n",
       " {'SKU': 100133267851,\n",
       "  'product_name': 'CUKTECH酷态科10号超级电能棒Plus15000毫安时充电宝兼容120W/100W大功率快充支持笔记本/手机/小米/苹果',\n",
       "  'store': 'CUKTECH酷态科京东自营旗舰店',\n",
       "  'positive_rate': '99%',\n",
       "  'positive_count': 5000,\n",
       "  'neutral_count': 34,\n",
       "  'negative_count': 63,\n",
       "  'negative_review_1': '刚买了两天就百亿补贴降价了 还不能保价 心里不痛快',\n",
       "  'negative_review_2': '不好贴，还长了，也不粘边，真的服了',\n",
       "  'negative_review_3': '就这样包装？该说jd节省还是怎么了，隔壁友商平台买的酷态科自带线是有外包装的。',\n",
       "  'negative_review_4': '开箱损，接口是歪的。',\n",
       "  'negative_review_5': '充电宝容量虚标，而且价格还卖得贵，而且充电达不到120W.'},\n",
       " {'SKU': 10136859742730,\n",
       "  'product_name': '犀狼【新国标3C认证 66W快充】充电宝20000毫安适用华为mate60/P70超大容量自带线可上飞机移动电源 【皓月白】66W超级快充+自带三线+温控芯片 【2025旗舰新品】顶配闪充芯片+提',\n",
       "  'store': '犀狼得意专卖店',\n",
       "  'positive_rate': '100%',\n",
       "  'positive_count': 200,\n",
       "  'neutral_count': 0,\n",
       "  'negative_count': 0,\n",
       "  'negative_review_1': '',\n",
       "  'negative_review_2': '',\n",
       "  'negative_review_3': '',\n",
       "  'negative_review_4': '',\n",
       "  'negative_review_5': ''},\n",
       " {'SKU': 10060515593137,\n",
       "  'product_name': '电沃达正品【120W超级快充丨顶配80000M】充电宝可上飞机20000毫安大容量移动电源适用于华为苹果小米等 尊享黑【超级快充+优质电芯】提速800% 20000M',\n",
       "  'store': '电沃达旗舰店',\n",
       "  'positive_rate': '96%',\n",
       "  'positive_count': 10000,\n",
       "  'neutral_count': 200,\n",
       "  'negative_count': 200,\n",
       "  'negative_review_1': '充不了多少电就没有了',\n",
       "  'negative_review_2': '两万的只能充一次手机',\n",
       "  'negative_review_3': '陪伴这么多年，第一次退货😳😳😳',\n",
       "  'negative_review_4': '没收到货，已经差不多两个星期了物流还是没更新',\n",
       "  'negative_review_5': '用了两天，那个线就坏了'}]"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "start_idx = 45\n",
    "end_idx = 50\n",
    "jd_reviews_path = f\"../data/jd_reviews_{start_idx+1:02d}_{end_idx:02d}.csv\"\n",
    "scrape_jd_reviews(\n",
    "    webdriver_path=webdriver_path,\n",
    "    cookie_path=cookie_path,\n",
    "    skus_csv_path=jd_skus_path,\n",
    "    output_csv_path=jd_reviews_path,\n",
    "    user_agent=ua,\n",
    "    start_idx=start_idx,\n",
    "    end_idx=end_idx,\n",
    "    headless=False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "242c5526",
   "metadata": {},
   "outputs": [],
   "source": [
    "driver = get_chromedriver(\n",
    "    webdriver_path=\"../../chromedriver.exe\",\n",
    "    disable_logging=True,\n",
    "    headless=False,\n",
    "    user_agent=ua,\n",
    "    disable_automation_features=True,\n",
    "    patch_webdriver_property=True,\n",
    "    implicit_wait=30\n",
    ")\n",
    "driver.get(\"https://www.jd.com/?country=USA\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d5d21a7f",
   "metadata": {},
   "outputs": [],
   "source": [
    "driver.quit()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b33d37d0",
   "metadata": {},
   "source": [
    "## Combine All CSV Files into One"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "74c3e15d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import pandas as pd\n",
    "from typing import List\n",
    "\n",
    "def combine_jd_reviews_csv(\n",
    "    data_dir: str = \"../data/\",\n",
    "    output_filename: str = \"jd_reviews_all.csv\"\n",
    ") -> None:\n",
    "    \"\"\"\n",
    "    合并指定目录下所有包含 'jd_reviews' 的 CSV 文件到一个总文件中，并保存为 jd_reviews_all.csv。\n",
    "    Combine all CSV files with 'jd_reviews' in their filename from the given directory\n",
    "    into a single CSV file named 'jd_reviews_all.csv'.\n",
    "\n",
    "    Args:\n",
    "        data_dir (str): 存放 CSV 文件的目录路径。Directory containing the review CSV files.\n",
    "        output_filename (str): 合并后输出文件名。Name of the combined output CSV file.\n",
    "    \"\"\"\n",
    "    # List all files in the data directory\n",
    "    files: List[str] = os.listdir(data_dir)\n",
    "    # Filter for CSV files that contain 'jd_reviews' in the filename\n",
    "    review_files = [f for f in files if f.endswith(\".csv\") and \"jd_reviews\" in f]\n",
    "\n",
    "    if not review_files:\n",
    "        print(f\"No 'jd_reviews' CSV files found in {data_dir}\")\n",
    "        return\n",
    "\n",
    "    # Read each CSV into a DataFrame\n",
    "    df_list: List[pd.DataFrame] = []\n",
    "    for filename in review_files:\n",
    "        path = os.path.join(data_dir, filename)\n",
    "        print(f\"Reading {path}...\")\n",
    "        df = pd.read_csv(path, encoding=\"utf-8-sig\")\n",
    "        df_list.append(df)\n",
    "\n",
    "    # Concatenate all DataFrames\n",
    "    combined_df = pd.concat(df_list, ignore_index=True)\n",
    "    output_path = os.path.join(data_dir, output_filename)\n",
    "\n",
    "    # Save the combined DataFrame to CSV\n",
    "    combined_df.to_csv(output_path, index=False, encoding=\"utf-8-sig\")\n",
    "    print(f\"Saved combined reviews to {output_path}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9286ad95",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reading ../data/jd_reviews_01_05.csv...\n",
      "Reading ../data/jd_reviews_06_10.csv...\n",
      "Reading ../data/jd_reviews_11_15.csv...\n",
      "Reading ../data/jd_reviews_16_20.csv...\n",
      "Reading ../data/jd_reviews_21_25.csv...\n",
      "Reading ../data/jd_reviews_26_30.csv...\n",
      "Reading ../data/jd_reviews_31_35.csv...\n",
      "Reading ../data/jd_reviews_36_40.csv...\n",
      "Reading ../data/jd_reviews_41_45.csv...\n",
      "Reading ../data/jd_reviews_46_50.csv...\n",
      "Saved combined reviews to ../data/jd_reviews_all.csv\n"
     ]
    }
   ],
   "source": [
    "combine_jd_reviews_csv(\n",
    "    data_dir=\"../data/\",\n",
    "    output_filename=\"jd_reviews_all.csv\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "911c68cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total reviews loaded: 50\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>SKU</th>\n",
       "      <th>product_name</th>\n",
       "      <th>store</th>\n",
       "      <th>positive_rate</th>\n",
       "      <th>positive_count</th>\n",
       "      <th>neutral_count</th>\n",
       "      <th>negative_count</th>\n",
       "      <th>negative_review_1</th>\n",
       "      <th>negative_review_2</th>\n",
       "      <th>negative_review_3</th>\n",
       "      <th>negative_review_4</th>\n",
       "      <th>negative_review_5</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>100024102298</td>\n",
       "      <td>罗马仕【热销200万+件】充电宝20000毫安22.5W快充 可上飞机苹果20W快充 适用华...</td>\n",
       "      <td>罗马仕京东自营旗舰店</td>\n",
       "      <td>97%</td>\n",
       "      <td>200000</td>\n",
       "      <td>2000</td>\n",
       "      <td>5000</td>\n",
       "      <td>此用户未填写评价内容</td>\n",
       "      <td>此用户未及时填写评价内容</td>\n",
       "      <td>此用户未及时填写评价内容</td>\n",
       "      <td>此用户未填写评价内容</td>\n",
       "      <td>此用户未及时填写评价内容</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>100096418777</td>\n",
       "      <td>小米（MI）小米自带线充电宝20000 33W 浅咖色 适用小米/红米/redmi/苹果/安...</td>\n",
       "      <td>小米京东自营旗舰店</td>\n",
       "      <td>98%</td>\n",
       "      <td>100000</td>\n",
       "      <td>1000</td>\n",
       "      <td>1000</td>\n",
       "      <td>充电过程中插头过热烫手</td>\n",
       "      <td>刚买了几天就发现降价了，体验感不好</td>\n",
       "      <td>此用户未填写评价内容</td>\n",
       "      <td>容量很一般，性价比不高！</td>\n",
       "      <td>嗯，那个壳被压的稀烂了，还是有点小</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>100097634241</td>\n",
       "      <td>CUKTECH酷态科电能块自带线20000毫安时移动电源55W快充充电宝可上飞机适用苹果16...</td>\n",
       "      <td>CUKTECH酷态科京东自营旗舰店</td>\n",
       "      <td>97%</td>\n",
       "      <td>20000</td>\n",
       "      <td>200</td>\n",
       "      <td>200</td>\n",
       "      <td>充电一个小时一直卡在99格</td>\n",
       "      <td>说好的可以上飞机呢，到机场都给我扣了</td>\n",
       "      <td>此用户未及时填写评价内容</td>\n",
       "      <td>国补的东西有点小瑕疵哈</td>\n",
       "      <td>刚买就降价了，还没有价保，幽默</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>100073745484</td>\n",
       "      <td>罗马仕30W自带线充电宝20000毫安时双向快充 支持苹果15可上飞机 适用于苹果华为手机平...</td>\n",
       "      <td>罗马仕京东自营旗舰店</td>\n",
       "      <td>98%</td>\n",
       "      <td>1000000</td>\n",
       "      <td>10000</td>\n",
       "      <td>10000</td>\n",
       "      <td>刚买1天就降价，看到心不爽</td>\n",
       "      <td>根本没收到货，快递丢了</td>\n",
       "      <td>此用户未填写评价内容</td>\n",
       "      <td>第一天买了第二天就降价了！</td>\n",
       "      <td>东西不错快递慢了一天</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>100140584252</td>\n",
       "      <td>倍思（Baseus）充电宝20000毫安自带线大容量22.5W快充极速数显适用于苹果15/1...</td>\n",
       "      <td>倍思（Baseus）京东自营旗舰店</td>\n",
       "      <td>98%</td>\n",
       "      <td>5000</td>\n",
       "      <td>90</td>\n",
       "      <td>89</td>\n",
       "      <td>买回来就那个上面就有划痕花了唉</td>\n",
       "      <td>才用了一次就充不上电</td>\n",
       "      <td>打开快递发现盒子已经被撕开的痕迹，明显这不是纯新包装</td>\n",
       "      <td>充满了，没用会自己掉电</td>\n",
       "      <td>早上八点开始充电，充到晚上九点五十，充到了27%。离谱！</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            SKU                                       product_name  \\\n",
       "0  100024102298  罗马仕【热销200万+件】充电宝20000毫安22.5W快充 可上飞机苹果20W快充 适用华...   \n",
       "1  100096418777  小米（MI）小米自带线充电宝20000 33W 浅咖色 适用小米/红米/redmi/苹果/安...   \n",
       "2  100097634241  CUKTECH酷态科电能块自带线20000毫安时移动电源55W快充充电宝可上飞机适用苹果16...   \n",
       "3  100073745484  罗马仕30W自带线充电宝20000毫安时双向快充 支持苹果15可上飞机 适用于苹果华为手机平...   \n",
       "4  100140584252  倍思（Baseus）充电宝20000毫安自带线大容量22.5W快充极速数显适用于苹果15/1...   \n",
       "\n",
       "               store positive_rate  positive_count  neutral_count  \\\n",
       "0         罗马仕京东自营旗舰店           97%          200000           2000   \n",
       "1          小米京东自营旗舰店           98%          100000           1000   \n",
       "2  CUKTECH酷态科京东自营旗舰店           97%           20000            200   \n",
       "3         罗马仕京东自营旗舰店           98%         1000000          10000   \n",
       "4  倍思（Baseus）京东自营旗舰店           98%            5000             90   \n",
       "\n",
       "   negative_count negative_review_1   negative_review_2  \\\n",
       "0            5000        此用户未填写评价内容        此用户未及时填写评价内容   \n",
       "1            1000       充电过程中插头过热烫手   刚买了几天就发现降价了，体验感不好   \n",
       "2             200     充电一个小时一直卡在99格  说好的可以上飞机呢，到机场都给我扣了   \n",
       "3           10000     刚买1天就降价，看到心不爽         根本没收到货，快递丢了   \n",
       "4              89   买回来就那个上面就有划痕花了唉          才用了一次就充不上电   \n",
       "\n",
       "            negative_review_3 negative_review_4             negative_review_5  \n",
       "0                此用户未及时填写评价内容        此用户未填写评价内容                  此用户未及时填写评价内容  \n",
       "1                  此用户未填写评价内容      容量很一般，性价比不高！             嗯，那个壳被压的稀烂了，还是有点小  \n",
       "2                此用户未及时填写评价内容       国补的东西有点小瑕疵哈               刚买就降价了，还没有价保，幽默  \n",
       "3                  此用户未填写评价内容     第一天买了第二天就降价了！                    东西不错快递慢了一天  \n",
       "4  打开快递发现盒子已经被撕开的痕迹，明显这不是纯新包装       充满了，没用会自己掉电  早上八点开始充电，充到晚上九点五十，充到了27%。离谱！  "
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reviews_df = pd.read_csv(\"../data/jd_reviews_all.csv\", encoding=\"utf-8-sig\")\n",
    "print(f\"Total reviews loaded: {len(reviews_df)}\")\n",
    "reviews_df.head()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "da-env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
